package com.ds.lens.data.controller;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.ds.lens.data.common.client.vo.LensExternalEventInVO;
import com.ds.lens.data.common.client.vo.LensMetricInVO;
import com.ds.lens.data.common.constant.AggregatorEnum;
import com.ds.lens.data.common.constant.DetectionTypeEnum;
import com.ds.lens.data.common.util.MetricUtils;
import com.ds.lens.data.service.*;
import com.ds.lens.data.service.esservice.ForecastDataService;
import com.ds.lens.data.vo.BaseOutVO;
import com.ds.lens.data.vo.enums.DataCommonVOMessageEnum;
import com.ds.lens.data.vo.factory.VOFactory;
import com.ds.lens.data.vo.in.v2.monitor.LensMonitorChannelInVO;
import com.ds.lens.data.vo.in.v2.monitor.LensMonitorTagsSearchVO;
import com.ds.lens.data.vo.in.v2.monitor.state.LensMonitorStateSaveInVO;
import com.ds.lens.data.vo.out.v2.integration.LensIntegrationDetailOutVO;
import com.ds.lens.data.vo.out.v2.monitor.LensMonitorDetailsOutVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;
import java.util.Map;

/**
 * @author WeiShaoying
 * @date 2019/11/6 下午2:32
 */
@RestController
@Api(value = "Lens monitor controller")
@RequestMapping(produces = {"application/json;charset=UTF-8"})
@Slf4j
public class LensMonitorInnerController {

    private static final String METRIC_NAME = "metricName";

    private static final String METRIC_VALUE = "metricValue";

    private Gson gson = new GsonBuilder().create();

    @Autowired
    LensMonitorService lensMonitorService;

    @Autowired
    LensMonitorStateService lensMonitorStateService;

    @Autowired
    LensScheduleDowntimeService lensScheduleDowntimeService;

    @Autowired
    LensIntegrationService lensIntegrationService;

    @Autowired
    LensMonitorChannelService lensMonitorChannelService;

    @Autowired
    LensQueryService lensQueryService;

    @Autowired
    LensApplicationService lensApplicationService;

    @Autowired
    LensExternalEventService lensExternalEventService;

    @Autowired
    ForecastDataService forecastDataService;

    @Autowired
    LensDatasourceService lensDatasourceService;


    @ApiOperation(value = "Get external event timeout tasks.")
    @GetMapping(value = "/lens/inner/external/event/timeout/tasks")
    public BaseOutVO getExternalEventTimeoutTasks() {
        return VOFactory.getSuccessBaseOutVO(lensExternalEventService.getExternalEventTasks());
    }

    @ApiOperation(value = "check external event timeout.")
    @GetMapping(value = "/lens/inner/external/event/timeout/notify")
    public BaseOutVO sendTimeoutNotice(@RequestParam("externalEventId") Long externalEventId) {
        lensExternalEventService.sendTimeoutNotice(externalEventId);
        return VOFactory.getSuccessBaseOutVO();
    }

    @ApiOperation(value = "Start lens external event")
    @PostMapping(value = "/lens/inner/external/event/start")
    public BaseOutVO startExternalEvent(@RequestBody LensExternalEventInVO lensExternalEventInVO) {
        lensExternalEventService.startExternalEvent(lensExternalEventInVO);
        return VOFactory.getSuccessBaseOutVO(true);
    }

    @ApiOperation(value = "Complete lens external event.")
    @PostMapping(value = "/lens/inner/external/event/complete")
    public BaseOutVO completeExternalEvent(@RequestBody LensExternalEventInVO lensExternalEventInVO) {
        lensExternalEventService.completeExternalEvent(lensExternalEventInVO);
        return VOFactory.getSuccessBaseOutVO(true);
    }

    @ApiOperation(value = "get monitor detail by tags and applicationId")
    @PostMapping(value = "/lens/inner/monitor/tags")
    public BaseOutVO getInnerMonitorDetailsByTags(@RequestBody @Valid LensMonitorTagsSearchVO tagsSearchVO) {
        return VOFactory.getSuccessBaseOutVO(lensMonitorService.getMonitorDetailsByTags(tagsSearchVO.getApplicationId(), tagsSearchVO.getTags()));
    }

    @ApiOperation(value = "get monitor detail")
    @GetMapping(value = "/lens/inner/monitor/get")
    public BaseOutVO getInnerMonitorDetails(@RequestParam("id") Long id) {
        return VOFactory.getSuccessBaseOutVO(lensMonitorService.getMonitorDetails(id));
    }

    @GetMapping(value = "/lens/inner/monitor/listAvailableMonitorState")
    public BaseOutVO listAvailableMonitorState() {
        return VOFactory.getSuccessBaseOutVO(lensMonitorService.listAvailableMonitorState());
    }

    @GetMapping(value = "/lens/inner/monitor/listUpdatedMonitorState")
    public BaseOutVO listUpdatedMonitorState(@RequestParam("lastModifyTime") Long lastModifiedTimeStamp) {
        return VOFactory.getSuccessBaseOutVO(lensMonitorService.listUpdatedMonitorState(lastModifiedTimeStamp));
    }

    @PostMapping(value = "/lens/inner/monitor/state/save")
    public BaseOutVO saveMonitorState(@RequestBody @Valid LensMonitorStateSaveInVO monitorStateSaveInVO) {
        return VOFactory.getSuccessBaseOutVO(lensMonitorStateService.saveMonitorState(monitorStateSaveInVO));
    }

    @GetMapping(value = "/lens/inner/monitor/channel/get")
    public BaseOutVO getMonitorChannel(@RequestParam("monitorId") Long monitorId, @RequestParam("version") Integer version,
                                       @RequestParam("target") String target, @RequestParam("type") Integer type) {
        return VOFactory.getSuccessBaseOutVO(lensMonitorChannelService.get(monitorId, version, target, type));
    }

    @PostMapping(value = "/lens/inner/monitor/channel/save")
    public BaseOutVO saveMonitorChannel(@RequestBody @Valid LensMonitorChannelInVO channelInVO) {
        return VOFactory.getSuccessBaseOutVO(lensMonitorChannelService.save(channelInVO));
    }

    @GetMapping(value = "/lens/inner/schedule/list")
    public BaseOutVO listSchedule() {
        return VOFactory.getSuccessBaseOutVO(lensScheduleDowntimeService.listAllSchedule());
    }

    @GetMapping(value = "/lens/inner/application/list")
    public BaseOutVO listApplication() {
        return VOFactory.getSuccessBaseOutVO(lensApplicationService.listAllApplication());
    }

    @GetMapping(value = "/lens/inner/application/get")
    public BaseOutVO getApplicationByKey(@RequestParam("appKey") String appKey) {
        return VOFactory.getSuccessBaseOutVO(lensApplicationService.findLensApplicationByKey(appKey));
    }

    @GetMapping(value = "/lens/inner/application/listUpdated")
    public BaseOutVO listUpdatedApplication(@RequestParam("lastModifyTime") Long lastModifiedTimeStamp) {
        return VOFactory.getSuccessBaseOutVO(lensApplicationService.listUpdatedApplication(lastModifiedTimeStamp));
    }

    @GetMapping(value = "/lens/inner/schedule/mute")
    public BaseOutVO muteSchedule(@RequestParam("id") Long id, @RequestParam("mute") Boolean mute) {
        lensScheduleDowntimeService.scheduleMute(id, mute);
        return VOFactory.getSuccessBaseOutVO();
    }

    @GetMapping(value = "/lens/inner/integration/get")
    public BaseOutVO getLensIntegrationById(@RequestParam("id") Long id) {
        LensIntegrationDetailOutVO vo = lensIntegrationService.findLensIntegrationsById(id);
        if (null != vo) {
            return VOFactory.getSuccessBaseOutVO(vo);
        }
        return VOFactory.getBaseOutVO(DataCommonVOMessageEnum.COMMON_FAILED.getCode(), DataCommonVOMessageEnum.COMMON_FAILED.getMessage());
    }

    @ApiOperation(value = "metric query")
    @GetMapping(value = "/lens/inner/metric/query/{monitorId}/{startTime}/{endTime}/{interval}")
    public BaseOutVO queryMetrics(@PathVariable("monitorId") Long monitorId, @PathVariable("startTime") Long startTime,
                                  @PathVariable("endTime") Long endTime, @PathVariable("interval") Long interval) {
        LensMonitorDetailsOutVO detailsOutVO = lensMonitorService.getMonitorDetails(monitorId);
        if (detailsOutVO == null) {
            return VOFactory.getBaseOutVO(DataCommonVOMessageEnum.COMMON_FAILED.getCode(), "Cannot get monitor by monitorId");
        }
        LensMetricInVO lensMetricInVO = new LensMetricInVO();
        lensMetricInVO.setAppId(detailsOutVO.getApplicationId());
        lensMetricInVO.setIntegrationId(detailsOutVO.getIntegrationId());
        lensMetricInVO.setMetricAggr(detailsOutVO.getDefine().getMetricAggr());
        lensMetricInVO.setMetricBy(detailsOutVO.getDefine().getMetricBy());
        lensMetricInVO.setMetricFilter(detailsOutVO.getDefine().getMetricFilter());
        lensMetricInVO.setMetricName(detailsOutVO.getDefine().getMetricName());
        lensMetricInVO.setScope(detailsOutVO.getDefine().getScope());
        lensMetricInVO.setDetectionMethod(detailsOutVO.getDetectionMethod());
        if (detailsOutVO.getDetectionMethod() != null && !detailsOutVO.getDetectionMethod().equals(DetectionTypeEnum.STATIC_STATE.getCode())) {
            Map<String, Object> thresholdLevel = detailsOutVO.getDefine().getThresholdLevel();
            lensMetricInVO.setThresholdLevel(thresholdLevel);
        }
        long duration = detailsOutVO.getDefine().getDuring().longValue();
        long current = System.currentTimeMillis() / 60000 * 60000 - detailsOutVO.getDefine().getDelayTime() * 60000;
        if (startTime == null) {
            lensMetricInVO.setStartTime(current - duration * 60000);
        } else {
            lensMetricInVO.setStartTime(startTime);
        }
        if (endTime == null) {
            lensMetricInVO.setEndTime(current);
        } else {
            lensMetricInVO.setEndTime(endTime);
        }
        if (interval == null) {
            lensMetricInVO.setInterval(detailsOutVO.getDefine().getWindowTime().longValue());
        } else {
            lensMetricInVO.setInterval(interval);
        }
        log.info("/lens/inner/metric/query/{}/{}/{}/{} generated LensMetricInVO is {}",
                monitorId, startTime, endTime, interval, lensMetricInVO);
        List<Map<String, Object>> results = lensQueryService.query(lensMetricInVO);

        return VOFactory.getSuccessBaseOutVO(normalizeResults(lensMetricInVO.getMetricName(), lensMetricInVO.getMetricAggr(), results));
    }

    @ApiOperation(value = "metric enhanced query")
    @PostMapping(value = "/lens/inner/metric/enhanceQuery")
    public BaseOutVO queryMetrics(@RequestBody @Valid LensMetricInVO lensMetricInVO) {
        log.info("/lens/inner/metric/enhanceQuery input is " + gson.toJson(lensMetricInVO));
        List<Map<String, Object>> results = lensQueryService.query(lensMetricInVO);
        return VOFactory.getSuccessBaseOutVO(normalizeResults(lensMetricInVO.getMetricName(), lensMetricInVO.getMetricAggr(), results));
    }

    @ApiOperation(value = "metric query and group metrics")
    @PostMapping(value = "/lens/inner/metric/queryAndGroup")
    public BaseOutVO queryAndGroupMetrics(@RequestBody @Valid LensMetricInVO lensMetricInVO) {
        log.info("/lens/inner/metric/queryAndGroup input is " + gson.toJson(lensMetricInVO));
        Map<String, Map<Long, Map<String, Object>>> results = lensQueryService.queryAndGroup(lensMetricInVO);
        for (String key : results.keySet()) {
            Map<Long, Map<String, Object>> list = normalizeResults(lensMetricInVO.getMetricName(), lensMetricInVO.getMetricAggr(), results.get(key));
            results.put(key, list);
        }
        return VOFactory.getSuccessBaseOutVO(results);
    }

    @ApiOperation(value = "forecast metric query")
    @GetMapping(value = "/lens/inner/forecast/metric/query/{monitorId}/{version}/{startTime}/{endTime}")
    public BaseOutVO queryForecastMetrics(@PathVariable("monitorId") Long monitorId,
                                          @PathVariable("version") Integer version,
                                          @PathVariable("startTime") Long startTime,
                                          @PathVariable("endTime") Long endTime) {
        log.info("/lens/inner/forecast/metric/query/{}/{}/{}/{} ",
                monitorId, version, startTime, endTime);
        return VOFactory.getSuccessBaseOutVO(forecastDataService.queryForecastDatas(monitorId, version, startTime, endTime));
    }

    @ApiOperation(value = "get datasource ")
    @GetMapping(value = "/lens/inner/datasource/get")
    public BaseOutVO getDatasource(@RequestParam("id") Long id) {
        return VOFactory.getSuccessBaseOutVO(lensDatasourceService.getDatasource(id));
    }

    private List<Map<String, Object>> normalizeResults(String metricName, Integer aggrInt, List<Map<String, Object>> results) {
        if (CollectionUtils.isEmpty(results)) {
            return results;
        }
        String key = MetricUtils.generateKey(metricName, AggregatorEnum.valueOf(aggrInt));
        for (Map<String, Object> map : results) {
            Object value = map.remove(key);
            if (value != null) {
                map.put(METRIC_NAME, key);
                map.put(METRIC_VALUE, value);
            }
        }
        return results;
    }

    private Map<Long, Map<String, Object>> normalizeResults(String metricName, Integer aggrInt, Map<Long, Map<String, Object>> results) {
        if (CollectionUtils.isEmpty(results)) {
            return results;
        }
        String key = MetricUtils.generateKey(metricName, AggregatorEnum.valueOf(aggrInt));
        for (Map<String, Object> tmp : results.values()) {
            Object value = tmp.remove(key);
            if (value != null) {
                tmp.put(METRIC_NAME, key);
                tmp.put(METRIC_VALUE, value);
            }
        }
        return results;
    }
}
